--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 7fc1a7f658028cc3d230acc5f286f67c113999f6
Parents : 92c1e50
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-07T05:40:56-05:00
feat(context-menu): implement NomadBrowserContextMenu for tab and page actions, including context menu options for viewing source, reloading, favoriting, and downloading pages
Changes
6 files changed, 455 insertions(+), 1 deletions(-)
Diff
diff --git a/meshchatx/src/frontend/components/nomadnetwork/NomadBrowserContextMenu.vue b/meshchatx/src/frontend/components/nomadnetwork/NomadBrowserContextMenu.vue
new file mode 100644
index 00000000..8aa3bd88
--- /dev/null
+++ b/meshchatx/src/frontend/components/nomadnetwork/NomadBrowserContextMenu.vue
@@ -0,0 +1,129 @@
+<!-- SPDX-License-Identifier: 0BSD AND MIT -->
+
+<template>
+ <Teleport to="body">
+ <ContextMenuPanel
+ v-click-outside="{
+ handler: () => {
+ if (!justOpened) $emit('close');
+ },
+ capture: true,
+ }"
+ :show="show"
+ :x="x"
+ :y="y"
+ >
+ <ContextMenuItem :disabled="!hasActivePage" @click="$emit('view-source')">
+ <MaterialDesignIcon icon-name="code-tags" class="size-5" />
+ <span>{{ $t("app.toggle_source") }}</span>
+ </ContextMenuItem>
+ <ContextMenuItem :disabled="!hasActivePage" @click="$emit('reload')">
+ <MaterialDesignIcon icon-name="refresh" class="size-5" />
+ <span>{{ $t("common.refresh") }}</span>
+ </ContextMenuItem>
+ <ContextMenuItem :disabled="!canFavourite" @click="$emit('favorite')">
+ <MaterialDesignIcon :icon-name="isFavourite ? 'star-off' : 'star'" class="size-5" />
+ <span>{{ isFavourite ? $t("nomadnet.remove_favourite") : $t("nomadnet.add_favourite") }}</span>
+ </ContextMenuItem>
+ <ContextMenuItem :disabled="!canDownloadPage" @click="$emit('download-page')">
+ <MaterialDesignIcon icon-name="download" class="size-5" />
+ <span>{{ $t("nomadnet.download_page") }}</span>
+ </ContextMenuItem>
+ <template v-if="showTabActions">
+ <ContextMenuDivider />
+ <ContextMenuSectionLabel>{{ $t("nomadnet.context_tabs") }}</ContextMenuSectionLabel>
+ <ContextMenuItem :disabled="!canCloseTabsRight" @click="$emit('close-tabs-right')">
+ <MaterialDesignIcon icon-name="tab-remove" class="size-5" />
+ <span>{{ $t("nomadnet.close_tabs_to_right") }}</span>
+ </ContextMenuItem>
+ <ContextMenuItem :disabled="!canCloseOtherTabs" @click="$emit('close-other-tabs')">
+ <MaterialDesignIcon icon-name="tab-minus" class="size-5" />
+ <span>{{ $t("nomadnet.close_other_tabs") }}</span>
+ </ContextMenuItem>
+ <ContextMenuItem :disabled="!canCloseAllTabs" @click="$emit('close-all-tabs')">
+ <MaterialDesignIcon icon-name="close-box-multiple-outline" class="size-5" />
+ <span>{{ $t("nomadnet.close_all_tabs") }}</span>
+ </ContextMenuItem>
+ </template>
+ </ContextMenuPanel>
+ </Teleport>
+</template>
+
+<script>
+import ContextMenuDivider from "../contextmenu/ContextMenuDivider.vue";
+import ContextMenuItem from "../contextmenu/ContextMenuItem.vue";
+import ContextMenuPanel from "../contextmenu/ContextMenuPanel.vue";
+import ContextMenuSectionLabel from "../contextmenu/ContextMenuSectionLabel.vue";
+import MaterialDesignIcon from "../MaterialDesignIcon.vue";
+
+export default {
+ name: "NomadBrowserContextMenu",
+ components: {
+ ContextMenuDivider,
+ ContextMenuItem,
+ ContextMenuPanel,
+ ContextMenuSectionLabel,
+ MaterialDesignIcon,
+ },
+ props: {
+ show: {
+ type: Boolean,
+ required: true,
+ },
+ x: {
+ type: Number,
+ required: true,
+ },
+ y: {
+ type: Number,
+ required: true,
+ },
+ justOpened: {
+ type: Boolean,
+ default: false,
+ },
+ hasActivePage: {
+ type: Boolean,
+ default: false,
+ },
+ canFavourite: {
+ type: Boolean,
+ default: false,
+ },
+ isFavourite: {
+ type: Boolean,
+ default: false,
+ },
+ canDownloadPage: {
+ type: Boolean,
+ default: false,
+ },
+ showTabActions: {
+ type: Boolean,
+ default: false,
+ },
+ canCloseTabsRight: {
+ type: Boolean,
+ default: false,
+ },
+ canCloseOtherTabs: {
+ type: Boolean,
+ default: false,
+ },
+ canCloseAllTabs: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ emits: [
+ "close",
+ "view-source",
+ "reload",
+ "favorite",
+ "download-page",
+ "close-tabs-right",
+ "close-other-tabs",
+ "close-all-tabs",
+ ],
+};
+</script>
diff --git a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkBrowser.vue b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkBrowser.vue
index d036fe57..a72ed442 100644
--- a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkBrowser.vue
+++ b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkBrowser.vue
@@ -26,6 +26,7 @@
@dragover.prevent="onTabDragOver(tabIndex)"
@drop.prevent="onTabDrop(tabIndex)"
@dragend="onTabDragEnd"
+ @contextmenu.prevent="openTabContextMenu($event, tab)"
>
<MaterialDesignIcon icon-name="earth" class="size-4 shrink-0 opacity-70" />
<span class="min-w-0 flex-1 truncate text-left">{{ tabTitle(tab) }}</span>
@@ -53,6 +54,7 @@
v-for="tab in tabs"
v-show="tab.id === activeTabId"
:key="tab.id"
+ :ref="(el) => setPageRef(tab.id, el)"
embedded
:tabs-enabled="tabsEnabled"
:destination-hash="tab.destinationHash"
@@ -62,11 +64,35 @@
@close-tab="closeTab(tab.id)"
/>
</div>
+
+ <NomadBrowserContextMenu
+ :show="contextMenu.show"
+ :x="contextMenu.x"
+ :y="contextMenu.y"
+ :just-opened="contextMenu.justOpened"
+ :has-active-page="contextMenuHasActivePage"
+ :can-favourite="contextMenuCanFavourite"
+ :is-favourite="contextMenuIsFavourite"
+ :can-download-page="contextMenuCanDownloadPage"
+ :show-tab-actions="showTabStrip"
+ :can-close-tabs-right="contextMenuCanCloseTabsRight"
+ :can-close-other-tabs="contextMenuCanCloseOtherTabs"
+ :can-close-all-tabs="tabs.length > 1"
+ @close="closeContextMenu"
+ @view-source="onContextViewSource"
+ @reload="onContextReload"
+ @favorite="onContextFavorite"
+ @download-page="onContextDownloadPage"
+ @close-tabs-right="onContextCloseTabsRight"
+ @close-other-tabs="onContextCloseOtherTabs"
+ @close-all-tabs="onContextCloseAllTabs"
+ />
</div>
</template>
<script>
import NomadNetworkPage from "./NomadNetworkPage.vue";
+import NomadBrowserContextMenu from "./NomadBrowserContextMenu.vue";
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import GlobalState from "../../js/GlobalState";
import GlobalEmitter from "../../js/GlobalEmitter";
@@ -77,6 +103,7 @@ export default {
name: "NomadNetworkBrowser",
components: {
NomadNetworkPage,
+ NomadBrowserContextMenu,
MaterialDesignIcon,
},
props: {
@@ -96,6 +123,23 @@ export default {
mediaQuery: null,
mediaQueryListener: null,
dragTabIndex: null,
+ pageRefs: {},
+ contextMenu: {
+ show: false,
+ justOpened: false,
+ x: 0,
+ y: 0,
+ tabId: null,
+ },
+ };
+ },
+ provide() {
+ return {
+ nomadBrowserTabActions: {
+ openContextMenu: this.openPageContextMenu,
+ closeContextMenu: this.closeContextMenu,
+ getContextTabId: () => this.contextMenu.tabId ?? this.activeTabId,
+ },
};
},
computed: {
@@ -115,6 +159,36 @@ export default {
const activeIndex = this.tabs.findIndex((tab) => tab.id === this.activeTabId);
return `${activeIndex}\u241e${tabs}`;
},
+ contextTabIndex() {
+ const tabId = this.contextMenu.tabId ?? this.activeTabId;
+ return this.tabs.findIndex((tab) => tab.id === tabId);
+ },
+ contextPageRef() {
+ const tabId = this.contextMenu.tabId ?? this.activeTabId;
+ return tabId != null ? this.pageRefs[tabId] || null : null;
+ },
+ contextMenuHasActivePage() {
+ const page = this.contextPageRef;
+ return Boolean(page?.selectedNode && page?.nodePagePath);
+ },
+ contextMenuCanFavourite() {
+ return Boolean(this.contextPageRef?.selectedNode?.destination_hash);
+ },
+ contextMenuIsFavourite() {
+ const page = this.contextPageRef;
+ const hash = page?.selectedNode?.destination_hash;
+ return hash ? page.isFavourite(hash) : false;
+ },
+ contextMenuCanDownloadPage() {
+ const page = this.contextPageRef;
+ return Boolean(page?.nodePageContent && page?.nodePagePath && !page?.isFailedPageContent?.(page.nodePageContent));
+ },
+ contextMenuCanCloseTabsRight() {
+ return this.contextTabIndex >= 0 && this.contextTabIndex < this.tabs.length - 1;
+ },
+ contextMenuCanCloseOtherTabs() {
+ return this.tabs.length > 1 && this.contextTabIndex >= 0;
+ },
},
watch: {
tabLayoutSignature() {
@@ -494,6 +568,90 @@ export default {
delete query.newTab;
this.$router.replace({ ...this.$route, query }).catch(() => {});
},
+ setPageRef(tabId, el) {
+ if (el) {
+ this.pageRefs[tabId] = el;
+ return;
+ }
+ delete this.pageRefs[tabId];
+ },
+ openTabContextMenu(event, tab) {
+ this.selectTab(tab.id);
+ this.contextMenu = {
+ show: true,
+ justOpened: true,
+ x: event.clientX,
+ y: event.clientY,
+ tabId: tab.id,
+ };
+ setTimeout(() => {
+ this.contextMenu.justOpened = false;
+ }, 50);
+ },
+ openPageContextMenu(event) {
+ this.contextMenu = {
+ show: true,
+ justOpened: true,
+ x: event.clientX,
+ y: event.clientY,
+ tabId: this.activeTabId,
+ };
+ setTimeout(() => {
+ this.contextMenu.justOpened = false;
+ }, 50);
+ },
+ closeContextMenu() {
+ this.contextMenu.show = false;
+ },
+ onContextViewSource() {
+ this.contextPageRef?.showPageSource?.();
+ this.closeContextMenu();
+ },
+ async onContextReload() {
+ await this.contextPageRef?.reloadNodePage?.();
+ this.closeContextMenu();
+ },
+ async onContextFavorite() {
+ await this.contextPageRef?.toggleFavouriteFromContext?.();
+ this.closeContextMenu();
+ },
+ async onContextDownloadPage() {
+ await this.contextPageRef?.downloadPageToDisk?.();
+ this.closeContextMenu();
+ },
+ onContextCloseTabsRight() {
+ const tabId = this.contextMenu.tabId ?? this.activeTabId;
+ this.closeTabsToRight(tabId);
+ this.closeContextMenu();
+ },
+ onContextCloseOtherTabs() {
+ const tabId = this.contextMenu.tabId ?? this.activeTabId;
+ this.closeOtherTabs(tabId);
+ this.closeContextMenu();
+ },
+ onContextCloseAllTabs() {
+ this.closeAllTabs();
+ this.closeContextMenu();
+ },
+ closeTabsToRight(tabId) {
+ const index = this.tabs.findIndex((tab) => tab.id === tabId);
+ if (index === -1) {
+ return;
+ }
+ const removeIds = this.tabs.slice(index + 1).map((tab) => tab.id);
+ removeIds.forEach((id) => this.closeTab(id));
+ },
+ closeOtherTabs(tabId) {
+ const keepId = tabId;
+ const removeIds = this.tabs.filter((tab) => tab.id !== keepId).map((tab) => tab.id);
+ removeIds.forEach((id) => this.closeTab(id));
+ this.selectTab(keepId);
+ },
+ closeAllTabs() {
+ this.tabs = [];
+ this.pageRefs = {};
+ this.addTab();
+ },
},
};
</script>
diff --git a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
index 9c217b34..185569ea 100644
--- a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
+++ b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
@@ -416,6 +416,7 @@
:style="nodeContainerShellStyle"
@click.capture="onElementClick"
@auxclick.capture="onElementClick"
+ @contextmenu.prevent="onPageContextMenu"
>
<!-- archived version notice -->
<div
@@ -572,6 +573,24 @@
</div>
</div>
</div>
+
+ <NomadBrowserContextMenu
+ v-if="!embedded"
+ :show="standaloneContextMenu.show"
+ :x="standaloneContextMenu.x"
+ :y="standaloneContextMenu.y"
+ :just-opened="standaloneContextMenu.justOpened"
+ :has-active-page="standaloneContextHasActivePage"
+ :can-favourite="Boolean(selectedNode?.destination_hash)"
+ :is-favourite="selectedNode ? isFavourite(selectedNode.destination_hash) : false"
+ :can-download-page="standaloneContextCanDownloadPage"
+ :show-tab-actions="false"
+ @close="closeStandaloneContextMenu"
+ @view-source="onStandaloneContextViewSource"
+ @reload="onStandaloneContextReload"
+ @favorite="onStandaloneContextFavorite"
+ @download-page="onStandaloneContextDownloadPage"
+ />
</div>
</template>
@@ -583,6 +602,7 @@ import { renderNomadPageByPath, resolveNomadPageShellBackground } from "../../js
import DialogUtils from "../../js/DialogUtils";
import WebSocketConnection from "../../js/WebSocketConnection";
import NomadNetworkSidebar from "./NomadNetworkSidebar.vue";
+import NomadBrowserContextMenu from "./NomadBrowserContextMenu.vue";
import Utils from "../../js/Utils";
import DownloadUtils from "../../js/DownloadUtils";
import ToastUtils from "../../js/ToastUtils";
@@ -605,6 +625,7 @@ export default {
name: "NomadNetworkPage",
components: {
NomadNetworkSidebar,
+ NomadBrowserContextMenu,
MaterialDesignIcon,
IconButton,
DropDownMenu,
@@ -632,6 +653,11 @@ export default {
},
},
emits: ["navigate", "open-node", "close-tab"],
+ inject: {
+ nomadBrowserTabActions: {
+ default: null,
+ },
+ },
data() {
return {
GlobalState,
@@ -703,6 +729,12 @@ export default {
nomadMicronWasmReady: false,
wasmBundled: isMicronWasmBundled(),
pageShellBackground: null,
+ standaloneContextMenu: {
+ show: false,
+ justOpened: false,
+ x: 0,
+ y: 0,
+ },
};
},
computed: {
@@ -710,6 +742,14 @@ export default {
const p = GlobalState.config?.nomad_default_page_path;
return typeof p === "string" && p.startsWith("/page/") ? p : "/page/index.mu";
},
+ standaloneContextHasActivePage() {
+ return Boolean(this.selectedNode && this.nodePagePath);
+ },
+ standaloneContextCanDownloadPage() {
+ return Boolean(
+ this.nodePageContent && this.nodePagePath && !this.isFailedPageContent(this.nodePageContent)
+ );
+ },
nomadMicronWasmFeatureEffective() {
return isMicronWasmBundled() && (GlobalState.config || {}).nomad_micron_wasm_enabled === true;
},
@@ -1945,6 +1985,72 @@ export default {
toggleNodePageSource() {
this.isShowingNodePageSource = !this.isShowingNodePageSource;
},
+ showPageSource() {
+ if (!this.nodePagePath) {
+ return;
+ }
+ this.isShowingNodePageSource = true;
+ },
+ async toggleFavouriteFromContext() {
+ if (!this.selectedNode?.destination_hash) {
+ return;
+ }
+ if (this.isFavourite(this.selectedNode.destination_hash)) {
+ await this.removeFavourite(this.selectedNode);
+ return;
+ }
+ await this.addFavourite(this.selectedNode);
+ },
+ async downloadPageToDisk() {
+ if (!this.nodePageContent || !this.nodePagePath || this.isFailedPageContent(this.nodePageContent)) {
+ ToastUtils.warning(this.$t("nomadnet.download_page_unavailable"));
+ return;
+ }
+ const parsed = this.parseNomadnetworkUrl(this.nodePagePath);
+ const pathPart = parsed?.pagePath || this.nodePagePath;
+ const segments = String(pathPart).split("/").filter(Boolean);
+ const filename = segments.length > 0 ? segments[segments.length - 1] : "nomad-page.txt";
+ const blob = new Blob([this.nodePageContent], { type: "text/plain;charset=utf-8" });
+ await DownloadUtils.downloadFile(filename, blob);
+ ToastUtils.success(this.$t("nomadnet.download_page_started"));
+ },
+ onPageContextMenu(event) {
+ if (this.embedded && this.nomadBrowserTabActions) {
+ this.nomadBrowserTabActions.openContextMenu(event);
+ return;
+ }
+ this.openStandaloneContextMenu(event);
+ },
+ openStandaloneContextMenu(event) {
+ this.standaloneContextMenu = {
+ show: true,
+ justOpened: true,
+ x: event.clientX,
+ y: event.clientY,
+ };
+ setTimeout(() => {
+ this.standaloneContextMenu.justOpened = false;
+ }, 50);
+ },
+ closeStandaloneContextMenu() {
+ this.standaloneContextMenu.show = false;
+ },
+ onStandaloneContextViewSource() {
+ this.showPageSource();
+ this.closeStandaloneContextMenu();
+ },
+ async onStandaloneContextReload() {
+ await this.reloadNodePage();
+ this.closeStandaloneContextMenu();
+ },
+ async onStandaloneContextFavorite() {
+ await this.toggleFavouriteFromContext();
+ this.closeStandaloneContextMenu();
+ },
+ async onStandaloneContextDownloadPage() {
+ await this.downloadPageToDisk();
+ this.closeStandaloneContextMenu();
+ },
async reloadNodePage() {
// reload current node page without adding to history and without using cache
this.onNodePageUrlClick(this.nodePagePath, null, false, false);
diff --git a/meshchatx/src/frontend/locales/en.json b/meshchatx/src/frontend/locales/en.json
index bfb6823b..3af39f07 100644
--- a/meshchatx/src/frontend/locales/en.json
+++ b/meshchatx/src/frontend/locales/en.json
@@ -1737,7 +1737,14 @@
"bulk_add_to_favourites": "Add to favourites",
"bulk_add_favourites_done": "Added {count} nodes to favourites",
"bulk_block_nodes": "Banish",
- "bulk_nothing_to_add_favourites": "Nothing to add (already favourites or none selected)"
+ "bulk_nothing_to_add_favourites": "Nothing to add (already favourites or none selected)",
+ "context_tabs": "Tabs",
+ "close_tabs_to_right": "Close tabs to the right",
+ "close_other_tabs": "Close other tabs",
+ "close_all_tabs": "Close all tabs",
+ "download_page": "Download page",
+ "download_page_started": "Page download started",
+ "download_page_unavailable": "No page content available to download"
},
"forwarder": {
"title": "LXMF Forwarder",
diff --git a/tests/frontend/NomadNetworkBrowser.test.js b/tests/frontend/NomadNetworkBrowser.test.js
index 69d2b10d..ba14551c 100644
--- a/tests/frontend/NomadNetworkBrowser.test.js
+++ b/tests/frontend/NomadNetworkBrowser.test.js
@@ -334,4 +334,34 @@ describe("NomadNetworkBrowser.vue", () => {
expect(wrapper.vm.tabs[2].title).toBe(orderBefore[0]);
expect(wrapper.vm.dragTabIndex).toBeNull();
});
+
+ it("closeTabsToRight removes tabs after the target tab", () => {
+ const wrapper = mountBrowser();
+ wrapper.vm.addTab("a".repeat(32), null, "A");
+ wrapper.vm.addTab("b".repeat(32), null, "B");
+ const first = wrapper.vm.tabs[0].id;
+ expect(wrapper.vm.tabs).toHaveLength(3);
+ wrapper.vm.closeTabsToRight(first);
+ expect(wrapper.vm.tabs).toHaveLength(1);
+ expect(wrapper.vm.tabs[0].id).toBe(first);
+ });
+
+ it("closeOtherTabs keeps only the target tab", () => {
+ const wrapper = mountBrowser();
+ wrapper.vm.addTab("a".repeat(32), null, "A");
+ const middle = wrapper.vm.tabs[0].id;
+ wrapper.vm.addTab("b".repeat(32), null, "B");
+ wrapper.vm.closeOtherTabs(middle);
+ expect(wrapper.vm.tabs).toHaveLength(1);
+ expect(wrapper.vm.tabs[0].id).toBe(middle);
+ });
+
+ it("closeAllTabs resets to a single new tab", () => {
+ const wrapper = mountBrowser();
+ wrapper.vm.addTab("a".repeat(32), null, "A");
+ wrapper.vm.addTab("b".repeat(32), null, "B");
+ wrapper.vm.closeAllTabs();
+ expect(wrapper.vm.tabs).toHaveLength(1);
+ expect(wrapper.vm.tabs[0].destinationHash).toBe("");
+ });
});
diff --git a/tests/frontend/NomadNetworkPage.test.js b/tests/frontend/NomadNetworkPage.test.js
index 0c134664..86a45481 100644
--- a/tests/frontend/NomadNetworkPage.test.js
+++ b/tests/frontend/NomadNetworkPage.test.js
@@ -62,6 +62,7 @@ describe("NomadNetworkPage.vue", () => {
template: '<div class="sidebar-stub"></div>',
props: ["nodes", "selectedDestinationHash"],
},
+ NomadBrowserContextMenu: true,
VTooltip: {
template: '<div class="v-tooltip-stub"><slot /></div>',
},
@@ -503,4 +504,27 @@ describe("NomadNetworkPage.vue", () => {
expect(payload.nomadnet_file_download).not.toHaveProperty("data");
});
});
+
+ describe("browser context menu actions", () => {
+ it("showPageSource enables source view when a page is loaded", () => {
+ const wrapper = mountNomadNetworkPage();
+ wrapper.vm.selectedNode = { destination_hash: "a".repeat(32), display_name: "Node" };
+ wrapper.vm.nodePagePath = `${"a".repeat(32)}:/page/index.mu`;
+ wrapper.vm.isShowingNodePageSource = false;
+ wrapper.vm.showPageSource();
+ expect(wrapper.vm.isShowingNodePageSource).toBe(true);
+ });
+
+ it("downloadPageToDisk saves current page content", async () => {
+ const DownloadUtils = (await import("@/js/DownloadUtils")).default;
+ const downloadFile = vi.spyOn(DownloadUtils, "downloadFile").mockResolvedValue(undefined);
+ const wrapper = mountNomadNetworkPage();
+ wrapper.vm.selectedNode = { destination_hash: "a".repeat(32), display_name: "Node" };
+ wrapper.vm.nodePagePath = `${"a".repeat(32)}:/page/index.mu`;
+ wrapper.vm.nodePageContent = "Hello Nomad";
+ await wrapper.vm.downloadPageToDisk();
+ expect(downloadFile).toHaveBeenCalledWith("index.mu", expect.any(Blob));
+ downloadFile.mockRestore();
+ });
+ });
});
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────